
import Konva from 'konva'
import { cloneDeep } from 'lodash'
import { compressToUTF16 } from 'lz-string'

class NodeBase extends Konva.Group {
  constructor(config) {
    const onlyShow = config.onlyShow
    // delete config.onlyShow
    super(config)
    if (!onlyShow) {
      this.init()
    }
  }
  init() {
    this.on('click', (e) => {
      // this.moveToTop()
      this.getStage()?.find('.hotZone').forEach(i => i.setAttr('strokeWidth', 0))
      this.getStage().find('.edit').forEach(i => i.destroy())
      this.hotZone.setAttr('strokeWidth', 1)
      this.getStage().setCurrent(this)
      deleteFn(this, () => {
        this.getStage().off('click.nodeActive')
        this.getStage().setCurrent(null)
      })
      this.getStage()?.on('click.nodeActive', (e) => {
        if (e.target.getName() == 'stage') {
          this.getStage().find('.hotZone').forEach(i => i.setAttr('strokeWidth', 0))
          this.getStage().off('click.nodeActive')
          this.off('dragmove.delete')
          this.getStage().findOne('#deleteGroup')?.destroy()
          this.getStage().setCurrent(null)
        }
      })
    })
  }
  addBox() {
    console.log('111')
    const xy = { x: Infinity, y: Infinity }
    this.children.forEach(i => {
      const { x, y } = i.getAbsolutePosition(this)
      if (x < xy.x) {
        xy.x = x
      }
      if (y < xy.y) {
        xy.y = y
      }
    })
    const { width, height } = this.getClientRect()
    const opt = {
      x: xy.x - 15,
      y: xy.y - 20,
      width: width + 30,
      height: height + 40,
      fill: '#232c35',
      stroke: 'red',
      strokeWidth: 0,
      cornerRadius: 5,
      opacity: 0.4,
      name: 'hotZone'
    }
    const box = new Konva.Rect(opt)
    this.add(box)
    box.moveToBottom()
    this.hotZone = box
  }

  update() {
    if (this.txt) {
      this.txt.setAttr('text', this.attrs.data.txt)
      this.hotZone.destroy()
      this.addBox()
      this.fire('click')
    }
  }
}

/** uuid生成器 */
export function uuid() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = (Math.random() * 16) | 0
    var v = c == 'x' ? r : (r & 0x3) | 0x8
    return v.toString(16)
  })
}

export class NodeTwo extends NodeBase {
  constructor(props) {
    super({
      x: props.x,
      y: props.y,
      draggable: !props.onlyShow,
      name: 'node2',
      id: props.id || uuid(),
      data: {
        txt: props.txt
      },
      onlyShow: props.onlyShow
    })
    this.props = props

    if (props.txt) {
      const fontSize = props.fontSize || 12
      this.fontSize1 = fontSize
      const txtWidth = new Konva.Text({ text: props.txt, fontSize }).getClientRect().width
      const txt = new Konva.Text({
        x: props.w / 2 - txtWidth / 2,
        y: props.h + 5,
        text: props.txt,
        fontSize: fontSize,
        fontFamily: 'Calibri',
        fill: 'rgba(255, 255, 255, 0.647058823529412)',
        width: txtWidth,
        height: fontSize,
        align: 'center',
        verticalAlign: 'middle'
      })
      this.add(txt)
      this.txt = txt
    }

    const img = new Konva.Image({
      name: 'img',
      id: uuid(),
      url: props.img,
      width: props.w,
      height: props.h

    })
    this.add(img)

    const fn = () => {
      return new Promise((resolve, reject) => {
        const image = new Image()
        image.src = props.img
        image.onload = () => {
          img.setAttrs({
            image
          })
          props.onlyShow || this.addBox()
          resolve()
        }
      })
    }
    if (props.onFinish) {
      props.onFinish(fn())
    } else {
      fn()
    }
  }
  update() {
    const txtWidth = new Konva.Text({ text: this.attrs.data.txt, fontSize: this.fontSize1 }).getClientRect().width
    this.txt.setAttrs({
      x: this.props.w / 2 - txtWidth / 2,
      width: txtWidth
    })

    super.update()
  }
}

export class NodeOne extends NodeBase {
  constructor(props) {
    super({
      x: props.x,
      y: props.y,
      draggable: !props.onlyShow,
      name: 'node1',
      id: props.id || uuid(),
      data: {
        txt: props.txt
      },
      onlyShow: props.onlyShow
    })

    if (props.txt) {
      const fontSize = props.fontSize || 14
      this.fontSize1 = fontSize
      const txtWidth = new Konva.Text({ text: props.txt, fontSize }).getClientRect().width

      const opt = {
        x: props.h + 3,
        y: 0,
        width: txtWidth + 20,
        height: props.h
      }

      const rect = new Konva.Rect({
        ...opt,
        fill: '#1a3845',
        stroke: '#208a98',
        strokeWidth: 1
      })

      this.add(rect)
      this.rect1 = rect

      const txt = new Konva.Text({
        ...opt,
        text: props.txt,
        fontSize: fontSize,
        fontFamily: 'Calibri',
        fill: '#fff',
        align: 'center',
        verticalAlign: 'middle'
      })
      this.txt = txt
      this.add(txt)
    }

    const img = new Konva.Image({
      name: 'img',
      id: uuid(),
      url: props.img,
      width: props.h,
      height: props.h
    })
    this.add(img)

    const fn = () => {
      return new Promise((resolve, reject) => {
        const image = new Image()
        image.src = props.img
        image.onload = () => {
          img.setAttrs({
            image: image
          })
          props.onlyShow || this.addBox()
          resolve()
        }
      })
    }

    if (props.onFinish) {
      props.onFinish(fn())
    } else {
      fn()
    }
  }
  update() {
    const txtWidth = new Konva.Text({ text: this.attrs.data.txt, fontSize: this.fontSize1 }).getClientRect().width
    this.rect1.setAttrs({
      width: txtWidth + 20
    })
    this.txt.setAttrs({
      width: txtWidth + 20
    })
    super.update()
  }
}

class Arrow extends Konva.Group {
  constructor(props) {
    super({
      id: uuid(),
      draggable: true,
      name: 'arrow'
    })
    const lineArrow = new Konva.Arrow({
      points: props.points,
      stroke: '#29eeff',
      fill: '#29eeff',
      strokeWidth: 1,
      lineCap: 'round',
      lineJoin: 'round',
      dash: [2, 2],
      hitStrokeWidth: 8
    })
    const ps = lineArrow.getClientRect()

    this.setAttrs({ x: ps.x, y: ps.y })
    const arr = []
    for (let i = 0; i < props.points.length; i += 2) {
      arr.push(props.points[i] - ps.x)
      arr.push(props.points[i + 1] - ps.y)
    }
    lineArrow.points(arr)
    this.add(lineArrow)
    this.lineArrow = lineArrow
    setTimeout(() => {
      this.edit()
    }, 0)

    this.on('click', this.edit)
  }
  edit() {
    this.moveToTop()
    this.getStage().find('.edit').forEach(i => i.destroy())
    this.getStage()?.find('.hotZone').forEach(i => i.setAttr('strokeWidth', 0))
    this.getStage().setCurrent(null)
    const { width, height } = this.lineArrow.getClientRect()
    const g1 = new Konva.Group({ name: 'edit' })
    this.add(g1)
    const r1 = new Konva.Rect({
      width,
      height,
      fill: 'transparent',
      stroke: 'red',
      strokeWidth: 1,
      cornerRadius: 5,
      ...(this.r1Attrs || {})
    })

    g1.add(r1)

    // 添加线段点
    const points = this.lineArrow.points()
    const move1 = () => {
      for (let i = 0; i < points.length; i += 2) {
        const x = points[i]
        const y = points[i + 1]
        const circle = new Konva.Circle({
          x: x,
          y: y,
          radius: 5,
          fill: ' transparent ',
          stroke: '#29eeff',
          strokeWidth: 1,
          name: 'point',
          draggable: true,
          perfectDrawEnabled: false,
          dragBoundFunc: () => {
            const c = circle.getAbsolutePosition(this)
            points[i] = c.x
            points[i + 1] = c.y
            this.lineArrow.points(points)
            const min = [Infinity, Infinity]
            for (let j = 0; j < points.length; j += 2) {
              if (min[0] > points[j]) {
                min[0] = points[j]
              }
              if (min[1] > points[j + 1]) {
                min[1] = points[j + 1]
              }
            }
            const { width, height } = this.lineArrow.getClientRect()

            r1.setAttrs({
              width,
              height,
              x: min[0] - 5,
              y: min[1] - 5
            })
            this.r1Attrs = r1.attrs
          }
        })

        g1.add(circle)
      }
    }

    move1()
    setTimeout(() => {
      this.getStage().on('click.lineArrow', (e) => this.save(e))
      deleteFn(this, () => {
        this.getStage().off('click.lineArrow')
      })
    }, 0)
  }

  save(e) {
    if (e.target.getName() == 'stage') {
      const eg = this.getStage().find('.edit')
      eg.forEach(i => i.destroy())
      this.getStage().off('click.lineArrow')
      this.off('dragmove.delete')
      this.getStage().findOne('#deleteGroup')?.destroy()
    }
  }
}

/**
 *
 * @param { import('konva').default.Stage } stage
 * @param { {x:number, y:number} } param1
 */
export const createArrowLine = (stage, { x, y, call }) => {
  const layer = stage.findOne('#layer')
  /** @type { Konva.Group } */
  const ag = stage.findOne('#arrowGroup')

  /** @type { Konva.Arrow } */
  let arrow = ag.findOne('#lineArrow')

  ag.moveToTop()

  /**
   *
   * @param { import('konva').default.KonvaEventObject<MouseEvent> } e
   */
  const move = (e) => {
    const { offsetX, offsetY } = e.evt
    const x = offsetX - stage.x()
    const y = offsetY - stage.y()

    const arr = arrow.attrs.points
    arr.splice(arr.length - 2, 2, x, y)
    arrow.points(arr)
  }

  const complete = () => {
    console.log('dblclick')
    ag.removeChildren()
    const arr = arrow.attrs.points
    arr.splice(-4, 4)

    const ar = new Arrow({ points: arr })
    layer.add(ar)

    // console.log('ar', ar.getAbsolutePosition())

    call()
    stage.off('mousemove', move)
    stage.off('dblclick', complete)
  }

  if (!arrow) {
    arrow = new Konva.Line({
      points: [x, y, x, y],
      stroke: '#29eeff',
      fill: '#29eeff',
      strokeWidth: 1,
      lineCap: 'round',
      lineJoin: 'round',
      dash: [8, 4],
      id: 'lineArrow'
    })
    ag.add(arrow)

    stage.on('mousemove', move)
    stage.on('dblclick', complete)
  } else {
    arrow.points([...arrow.attrs.points, x, y])
  }
}

/**
 * 删除元素
 * @param { Konva.Group } shape
 */
const deleteFn = (shape, call = () => {}) => {
  shape.getStage().findOne('#deleteGroup')?.destroy()
  const rc = shape.getClientRect()
  const pos = shape.getStage().getAttrs()
  const g = new Konva.Group({
    id: 'deleteGroup',
    x: -pos.x + rc.x + rc.width / 2 - 25,
    y: -pos.y + rc.y + rc.height + 10
  })
  const r1 = new Konva.Rect({
    width: 50,
    height: 20,
    fill: 'transparent',
    stroke: 'yellow',
    strokeWidth: 1,
    cornerRadius: 5
  })
  g.add(r1)
  const txt = new Konva.Text({
    text: '删除',
    fontSize: 14,
    fill: 'yellow',
    align: 'center',
    verticalAlign: 'middle',
    width: 50,
    height: 20
  })
  g.add(txt)
  shape.getLayer().add(g)
  shape.on('dragmove.delete', () => {
    const rc = shape.getClientRect()
    const pos = shape.getStage().getAttrs()
    g.setAttrs({
      x: -pos.x + rc.x + rc.width / 2 - 25,
      y: -pos.y + rc.y + rc.height + 10
    })
  })
  g.on('click', () => {
    call()
    shape.destroy()
    g.destroy()
  })
}

/**
 * @param { Konva.Stage } stage
 * @param { {x:number, y:number} } param1
 */
export const createText = (stage, { x, y, call }) => {
  //
  const container = stage.container()

  const fn = (x, y, id) => {
    const input = document.createElement('div')
    input.style.position = 'absolute'
    input.style.left = x + 'px'
    input.style.top = y + 'px'
    input.style.background = 'transparent'
    // input.style.background = 'green'
    input.style.minWidth = '20px'
    input.style.minHeight = '14px'
    input.style.lineHeight = '14px'
    input.contentEditable = 'true'
    input.style.color = 'white'
    input.style.fontSize = '12px'
    input.style.whiteSpace = 'pre-wrap'
    container.appendChild(input)
    input.focus()

    /** @type { Konva.Group} } */
    let g = stage.findOne('#layer').findOne('#' + id)

    if (g) {
      const text = g.children.map(i => i.getAttr('text')).join('\n')

      input.innerText = text
      g.destroyChildren()
    } else {
      g = new Konva.Group({
        x: x - stage.x(),
        y: y - stage.y(),
        id: uuid(),
        draggable: true
      })
      stage.findOne('#layer').add(g)
    }

    input.onblur = (e) => {
      const text = input.innerText
      if (!text) {
        g.destroy()
        container.removeChild(input)
        call()
        return
      }

      text.split(/[\r\n]/).forEach((i, index) => {
        console.log('i', i)
        const t = new Konva.Text({
          y: 14 * index,
          text: i,
          fontSize: 12,
          fill: 'white',
          align: 'center',
          verticalAlign: 'middle',
          height: 14
        })
        g.add(t)
      })
      g.on('dblclick', (e) => {
        g.off('dblclick')
        const xy = g.getAbsolutePosition(stage)
        const x = xy.x + stage.x()
        const y = xy.y + stage.y()
        fn(x, y, g.id())
      })
      container.removeChild(input)
      call()
    }
  }

  fn(x + stage.x(), y + stage.y())
}

/** 生成场景数据
 * @param { Konva.Stage } stage
 */
export const generate = (stage1) => {
  return () => {
    console.log('456', stage1)

    const stage = cloneDeep(stage1)

    stage.find('.hotZone').forEach(i => i.destroy())
    stage.find('.edit').forEach(i => i.destroy())
    stage.findOne('#deleteGroup')?.destroy()

    const n1 = stage.find('.node1')
    const n2 = stage.find('.node2')
    const a = stage.find('.arrow')

    const arr = [n1, n2, a]
    arr.forEach(i => {
      i.forEach(j => j.setAttr('draggable', false))
    })

    const stageTemp = cloneDeep(stage)
    stageTemp.findOne('#arrowGroup').destroy()
    const arr2 = [...stageTemp.find('.node1'), ...stageTemp.find('.node2')]
    arr2.forEach(i => delete i.attrs.data)
    const jsonStr = stageTemp.toJSON()

    const arr1 = lineLink([...n1, ...n2], a, stage)
    // console.log('jsonStr:', jsonStr)
    return [arr1, compressToUTF16(jsonStr)]
  }
}

/**
 * link
 * @param {Konva.Group[]} shape
 * @param { Konva.Group[] } line
 */
const lineLink = (shape, line, stage) => {
  const obj = {}

  line.forEach(i => {
    const points = i.children[0].points()

    const start = points.slice(0, 2)
    const end = points.slice(-2)
    const { x, y } = i.getAbsolutePosition(stage)
    start[0] += x
    start[1] += y
    end[0] += x
    end[1] += y
    shape.some(j => {
      const xy = { x: Infinity, y: Infinity }
      j.children.forEach(i => {
        const { x, y } = i.getAbsolutePosition(stage)
        if (x < xy.x) {
          xy.x = x
        }
        if (y < xy.y) {
          xy.y = y
        }
      })
      const { width, height } = j.getClientRect()

      const x = xy.x
      const y = xy.y
      if (start[0] >= x - 15 && start[0] <= x + width + 15 && start[1] >= y - 20 && start[1] <= y + height + 20) {
        obj[i.id()] ||= { start: '', end: '' }
        obj[i.id()].start = j
      }
      if (end[0] >= x - 15 && end[0] <= x + width + 15 && end[1] >= y - 20 && end[1] <= y + height + 20) {
        obj[i.id()] ||= { start: '', end: '' }
        obj[i.id()].end = j
      }
    })
  })

  const ar = Object.values(obj)

  const a1 = ar.filter(i => i.start?.attrs?.name == 'node1' && i.end?.attrs?.name == 'node1')

  const sortFn = (arr) => {
    const sort1 = []
    arr.forEach(i => {
      const index1 = sort1.indexOf(i.start)
      const index2 = sort1.indexOf(i.end)
      if (index1 == -1 && index2 == -1) {
        sort1.push(i.start)
        sort1.push(i.end)
      } else if (index1 == -1 && index2 != -1) {
        if (index2 == 0) {
          sort1.unshift(i.start)
        } else {
          sort1.splice(index2 - 1, 0, i.start)
        }
      } else if (index1 != -1 && index2 == -1) {
        sort1.splice(index1 + 1, 0, i.end)
      }
    })
    return sort1
  }

  let sort1 = sortFn(a1)

  if (sort1.length == 0) {
    sort1 = ar.filter(i => i.start?.attrs?.name == 'node1').map(i => i.start)
  }

  const select = (i) => {
    const arr = [i.end]
    const fn = (x) => {
      const temp = ar.find(y => x.end == y.start)
      if (temp) {
        arr.push(temp.end)
        fn(temp)
      }
    }
    fn(i)
    return arr
  }

  sort1.forEach(i => {
    const a2 = ar.find(j => j.start == i && j.end?.attrs?.name == 'node2')
    if (a2) {
      const b = select(a2)
      i.attrs.children = b || []
    }
  })

  return sort1
}
